package com.et.machine.manager.api

import android.util.Log
import androidx.annotation.IntDef
import androidx.annotation.Keep
import com.et.drive.out.IOutGoodsImpl
import com.et.logger.ELog
import com.et.logger.TAGS
import com.et.machine.manager.api.Inspector.Companion.OUT_CODE_SEND_BOARD_TIME_OUT
import com.et.machine.manager.api.OutGoodsManager.OutControl.Companion.OUT_IDLE
import com.et.machine.manager.api.OutGoodsManager.OutControl.Companion.OUT_SDK_RESPONSE_RECEIVED
import com.et.machine.manager.api.OutGoodsManager.OutControl.Companion.OUT_SEND_TO_SDK
import java.util.concurrent.Executors
import java.util.concurrent.LinkedBlockingQueue
import java.util.concurrent.TimeUnit
import kotlin.random.Random

@Keep
open class OutGoodsManager {

    private val loopExecutor = Executors.newSingleThreadScheduledExecutor()
    private val outParamQueue = LinkedBlockingQueue<AbstractOutParam>()
    private val runnable = Runnable {
        loop()
        OutResultUpload.getInstance().uploadTrigger()
    }

    private val outControl: OutControl = OutControl(OUT_IDLE, null)

    init {
        ELog.d(TAGS.machine, "machine:OutGoodsManager>>init()")
        loopExecutor.scheduleWithFixedDelay(runnable, 1, 1, TimeUnit.SECONDS)
        IOutGoodsImpl.subscribe {
            outControl.code = OUT_IDLE
            outControl.param?.let { param ->
                val code = 0
                param.inspector.changeInspectorSuccessStatus(code)
                if (param is OutGoodsParamAndroid) {
                    param.parseOutGoodsResult()
                } else if (param is OutGoodsParamVMC) {
                    param.parseOutGoodsTcpResult()
                }
            }
            // TODO 760076
        }
    }

    /**
     * offer out-goods command
     */
    fun commitOutGoodsTask(param: AbstractOutParam) {
        ELog.d(TAGS.machine, "machine:OutGoodsManager>>commitOutGoodsTask")
        param.index = outParamQueue.size
        param.inspector = OutGoodsOrderInspector.getInstance().inspector(param)
        param.inspector.passed.let { outParamQueue.offer(param) }
    }

    private fun loop() {
        Log.d(TAGS.machine, "loop runnable")
        when (outControl.code) {
            OUT_IDLE -> {
                outParamQueue.poll(MIN_UPLOAD_TIME, TimeUnit.SECONDS)?.apply {
                    outControl.code = OUT_SEND_TO_SDK
                }
            }
            OUT_SEND_TO_SDK -> {
                outControl.code = OUT_SDK_RESPONSE_RECEIVED
                catTime()
            }
            OUT_SDK_RESPONSE_RECEIVED -> {
                catTime()
            }
        }
    }

    private fun catTime() {
        if (outControl.param == null) {
            outControl.code = OUT_IDLE
            return
        }
        outControl.param?.let { param ->
            param.controlInfo.addTimes()
            if (param.controlInfo.timeOut()) {
                // first TODO delete sdk command
                Random.nextBoolean().let { success ->
                    if (param is OutGoodsParamAndroid) {
                        if (success) {
                            param.inspector.changeInspectorFailureStatus(
                                OUT_CODE_SEND_BOARD_TIME_OUT
                            )
                            OutResultUpload.getInstance()
                                .offerUploadResult(param.parseOutGoodsResult())
                        } else {
                            param.inspector.changeInspectorFailureStatus(Inspector.OUT_CODE_OVER_TIME_OUT_GOODS)
                            param.parseOutGoodsResult()
                        }
                    }
                }
                outControl.code = OUT_IDLE
            }
        }
    }

    data class OutControl(@OutControlCode var code: Int, var param: AbstractOutParam?) {
        companion object {
            /**
             * out-goods control
             */
            const val OUT_IDLE = 0
            const val OUT_SEND_TO_SDK = 1
            const val OUT_SDK_RESPONSE_RECEIVED = 2

            @IntDef(OUT_IDLE, OUT_SEND_TO_SDK, OUT_SDK_RESPONSE_RECEIVED)
            annotation class OutControlCode
        }
    }

    companion object {
        private const val MIN_UPLOAD_TIME = 4L // 4s


//        private fun outGoods(
//            box: Int,
//            channel: Int,
//            cent: Int,
//            pay: Int,
//            serial: Int,
//            card: Int = 0
//        ): Boolean {
//            ELog.d(
//                TAGS.machine,
//                "OutGoods Request:[box,$box],[channel,$channel],[priceCent,$cent],[pay,${
//                    pay.toString(16)
//                }],[card,$card]"
//            )
//            val p = OutGoodsParam()
//            when (BuildConfig.FLAVOR) {
//                "et" -> {
//                    val serialByteArray = serial.toByteArray(3)
//                    val cardByteArray = card.toByteArray(10)
//                    val data = byteArrayOf(
//                        0,
//                        0,
//                        box.toByte(),
//                        1,
//                        channel.toByte(),
//                        cent.shr(16).toByte(),
//                        cent.shr(8).toByte(),
//                        cent.toByte(),
//                        pay.toByte(),
//                        serialByteArray[0],
//                        serialByteArray[1],
//                        serialByteArray[2],
//                        cardByteArray[0],
//                        cardByteArray[1],
//                        cardByteArray[2],
//                        cardByteArray[3],
//                        cardByteArray[4],
//                        cardByteArray[5],
//                        cardByteArray[6],
//                        cardByteArray[7],
//                        cardByteArray[8],
//                        cardByteArray[9],
//                        0
//                    )
//                    p.byteArray = data
//                }
//                "dc" -> {
//
//                }
//                "fs" -> {
//                }
//            }
//            val num = IOutGoodsImpl.outGoods(p)
//            if (num == OutGoodsStatusEnum.SUCCESS) return true else {
//                ELog.d(TAGS.machine, "Out Send Fault:$num")
//            }
//            return false
//        }

        @JvmStatic
        fun getInstance() = Holder.ins
    }

    object Holder {
        val ins = OutGoodsManager()
    }
}

abstract class AbstractOutParam(
    @PayType val payType: Int,
    val channelInfo: ChannelInfo,
    val controlInfo: Control
) {

    var index: Int = Int.MAX_VALUE
    lateinit var inspector: Inspector

    /**
     * channel info
     * */
    data class ChannelInfo(
        var box: Int,
        var channel: Int,
        val priceDime: Int,
    ) {
        var serialNo: Int = Int.MIN_VALUE
    }

    /**
     * control info
     * */
    class Control(private val TIME_MAX: Int) {

        private var times: Int = 0

        fun timeOut(): Boolean = times > TIME_MAX

        fun addTimes() {
            times++
        }

        companion object {
            private const val TIME_OUT_WAIT_ANDROID = 130
            private const val TIME_OUT_WAIT_VMC = 5
            private const val TIME_OUT_WAIT_TEST = 90

            fun createForAndroid(): Control {
                return Control(TIME_OUT_WAIT_ANDROID)
            }

            fun createForVmc(): Control {
                return Control(TIME_OUT_WAIT_VMC)
            }

            fun createForTest(): Control {
                return Control(TIME_OUT_WAIT_TEST)
            }
        }
    }


    /* Pay Type */
    @IntDef(PAY_50, PAY_41, PAY_42, PAY_47, PAY_72)
    annotation class PayType

    companion object {
        const val PAY_50 = 0x50
        const val PAY_41 = 0x41
        const val PAY_42 = 0x42
        const val PAY_47 = 0x47
        const val PAY_72 = 0x72
    }
}

@Keep
class OutGoodsParamTest(box: Int, channel: Int, priceDime: Int) : AbstractOutParam(
    PAY_50,
    ChannelInfo(box, channel, priceDime).apply {
        serialNo = box * 100 + channel
    },
    controlInfo = Control.createForTest()
) {

}

class OutGoodsParamAndroid(
    @PayType payType: Int,
    box: Int, channel: Int, priceDime: Int,
) : AbstractOutParam(
    payType,
    ChannelInfo(box, channel, priceDime),
    Control.createForAndroid()
) {

    lateinit var orderInfo: OutGoodsOrderInfo

    data class OutGoodsOrderInfo(
        val outTradeNo: String,
        val flag: String,
    )

    fun parseOutGoodsResult(): HttpUploadParam {

        return HttpUploadParam(
            inspector.getPayCode(), inspector.getOutCode()
        )
    }

    companion object {
        fun parse(info: OutGoodsSingleInfo): OutGoodsParamAndroid {
            return OutGoodsParamAndroid(
                payType = info.payType,
                info.box,
                info.channel,
                info.priceCent / 10,
            ).apply {
                orderInfo = OutGoodsOrderInfo(info.outTradeNo, info.flag)
            }
        }
    }

    // { "gmt_payment":"2021-07-21 04:14:39", "qrcode_id":"93015837gEsST2oi528PVnxlgMEtDpCG", "avm":"93015837", "type":"01", "rows":[{ "out_trade_no":"93015837-20210721041435058-42", "flag":"93015837-20210721041435058-42-946-1", "actualmoney":200, "aisleNo":"20", "price":200, "pay":"42", "box":"11" }], "board":"303337550a3732334231db6a" }

    data class OutGoodsSingleInfo(
        val payType: Int,
        val box: Int,
        val channel: Int,
        val priceCent: Int,
        val outTradeNo: String,
        val flag: String
    )
}

class OutGoodsParamVMC(box: Int, channel: Int, priceDime: Int, @PayType payType: Int) :
    AbstractOutParam(
        payType,
        channelInfo = ChannelInfo(box, channel, priceDime),
        controlInfo = Control.createForVmc()
    ) {

    fun parseOutGoodsTcpResult(): TcpUploadParam {
        return TcpUploadParam(byteArrayOf())
    }
}


